﻿
using Devil;
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameToolkit
{
    /// <summary>
    /// 定义可以通过 json 序列化/反序列化的资源类型
    /// </summary>
    [System.Diagnostics.Conditional("UNITY_EDITOR")]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class JsonImporterAttribute : Attribute
    {
        public string FormerlySerializedAs { get; set; }
    }

    /// <summary>
    /// 定义是否可以通过 Clone API 复制属性
    /// </summary>
    [System.Diagnostics.Conditional("UNITY_EDITOR")]
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = false)]
    public class JsonCloneableAttribute : Attribute
    {
        public bool IsCloneable { get; private set; }
        public JsonCloneableAttribute(bool isCloneable)
        {
            IsCloneable = isCloneable;
        }
    }

#if UNITY_EDITOR
    /// <summary>
    /// 当需要通过 Json 文件作为资源格式导入项目时，该对象提供了相应的资源序列化/反序列化方法
    /// (仅 Editor 可用)
    /// </summary>
    sealed public partial class JsonSerializedObject
    {

        [MenuItem("CONTEXT/Object/Print Asset's GUID")]
        static void GetGUID(MenuCommand context)
        {
            var asset = context.context;
            string aname = asset.name;
            if (asset is AssetImporter importor)
            {
                aname = importor.assetPath;
                asset = AssetDatabase.LoadMainAssetAtPath(importor.assetPath);
            }
            string guid;
            long localId;
            if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out guid, out localId))
            {
                Debug.LogFormat(asset, "Information about \"{0}\"\nguid: {1}, localId: {2}", aname, guid, localId);
            }
        }

        #region serialization

        readonly static object[] NONE_ARGS = new object[0];
        public class SerializationContext
        {
            bool mCloneableOnly;
            object mAsset;
            //Dictionary<int, object> mLocalFiles;
            ArrayList mLocalFiles;
            // 本地副本映射
            Dictionary<object, object> mLocalCloneMap;
            internal object MainAsset => mAsset;
            internal bool IsCloneableFieldsOnly => mCloneableOnly;

            internal SerializationContext(bool cloneableFieldsOnly)
            {
                //mLocalFiles = new Dictionary<int, object>();
                mLocalFiles = new ArrayList();
                mCloneableOnly = cloneableFieldsOnly;
            }

            internal void SetClonedValue(object from, object cloneInstance)
            {
                if (from == null)
                    return;
                if (mLocalCloneMap == null)
                    mLocalCloneMap = new Dictionary<object, object>();
                mLocalCloneMap[from] = cloneInstance;
            }

            internal object GetClonedInstance(object from)
            {
                object v;
                if (from != null && mLocalCloneMap != null && mLocalCloneMap.TryGetValue(from, out v))
                    return v;
                else
                    return null;
            }

            internal object GetLocalFile(int id)
            {
                if (id <= mLocalFiles.Count && id > 0)
                    return mLocalFiles[id - 1];
                else
                    return null;
            }
            internal int GetLocalFileID(object inst)
            {
                if (inst == null)
                    return 0;
                for (int i = 0; i < mLocalFiles.Count; i++)
                {
                    if (inst.Equals(mLocalFiles[i]))
                        return i + 1;
                }
                mLocalFiles.Add(inst);
                return mLocalFiles.Count;
            }

            internal void ImportLocalFiles<T>(T asset, JsonData data) where T : class
            {
                if (data == null)
                    return;
                mAsset = asset;
                mLocalFiles.Clear();
                var lst = new ArrayList();
                foreach (var key in data.Keys)
                {
                    var value = data[key];
                    var wrap = GetWrapper((string)value["class"]);
                    if (wrap == null)
                    {
                        continue;
                    }
                    wrap.InitFields();
                    var target = wrap.Create();
                    if (target != null)
                    {
                        var id = Localization.ParseInt(key);
                        while (mLocalFiles.Count < id)
                        {
                            mLocalFiles.Add(null);
                        }
                        mLocalFiles[id - 1] = target;
                        lst.Add(wrap);
                        lst.Add(target);
                        lst.Add(value["body"]);
                    }
                }
                for (int i = 0; i < lst.Count; i += 3)
                {
                    var wrap = (TypeWrapper)lst[i];
                    var target = lst[i + 1];
                    var content = (JsonData)lst[i + 2];
                    wrap.Import(this, target, content);
                }
            }

            internal JsonData ExportLocalFiles<T>(T asset) where T : class
            {
                if (mLocalFiles.Count == 0)
                    return null;
                mAsset = asset;
                var dt = new JsonData(JsonType.Object);
                int index = 0;
                while (index < mLocalFiles.Count)
                //foreach (var kv in mLocalFiles)
                {
                    var target = mLocalFiles[index++];
                    if (target == asset)
                        continue;
                    TypeWrapper wrap = target == null ? null : GetWrapper(target.GetType());
                    if (wrap == null)
                    {
                        continue;
                    }
                    wrap.InitFields();
                    var data = new JsonData();
                    data["class"] = wrap.Name;
                    data["body"] = wrap.Export(this, target);
                    dt[Localization.ToString(index)] = data;
                }
                return dt;
            }
        }


        /// <summary>
        /// 导入 json 数据为特定类型资源
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <returns></returns>
        public static T ImportFromJson<T>(JsonData json) where T : class
        {
            return ImportFromJson(json, typeof(T)) as T;
        }

        public static object ImportFromJson(JsonData json, Type type)
        {
            InitWrappers();
            TypeWrapper wrap = GetWrapper(type);
            if (wrap == null)
            {
                Debug.LogError($"Failed to import {type.Name} from json (unsupport JsonWrapType<{type.FullName}>).");
                return null;
            }
            wrap.InitFields();
            if (wrap.IsRecursiveWrapper)
            {
                Debug.LogError($"Failed to import {type.Name} from json (JsonWrapType<{type.FullName}> is recursive serialization).");
                return null;
            }
            var context = new SerializationContext(false);
            var asset = ScriptableObject.CreateInstance(type);
            context.ImportLocalFiles(asset, json["instance"]);
            wrap.Import(context, asset, json["asset"]);
            return asset;
        }

        public static JsonData ExportFrom(object asset)
        {
            if (asset == null)
                return null;
            var type = asset.GetType();
            InitWrappers();
            TypeWrapper wrap = GetWrapper(type);
            if (wrap == null)
            {
                Debug.LogError($"Failed to export to json (unsupport JsonWrapType<{type.FullName}>).");
                return null;
            }
            wrap.InitFields();
            if (wrap.IsRecursiveWrapper)
            {
                Debug.LogError($"Failed to import {type.Name} from json (JsonWrapType<{type.FullName}> is recursive serialization).");
                return null;
            }
            var context = new SerializationContext(false);
            var data = wrap.Export(context, asset);
            var export = new JsonData(JsonType.Object);
            export["asset"] = data;
            export["instance"] = context.ExportLocalFiles(asset);
            return export;
        }

        public static object Clone(object asset, bool cloneableFieldsOnly)
        {
            if (asset == null)
                return null;
            var type = asset.GetType();
            InitWrappers();
            var wrap = GetWrapper(type);
            if (wrap == null)
            {
                Debug.LogError($"Failed to clone instance (unsupport JsonWrapType<{type.FullName}>).");
                return null;
            }
            wrap.InitFields();
            if (wrap.IsRecursiveWrapper)
            {
                Debug.LogError($"Failed to clone {type.Name} from another instance (JsonWrapType<{type.FullName}> is recursive serialization).");
                return null;
            }
            var context = new SerializationContext(cloneableFieldsOnly);
            var inst = wrap.Create();
            context.SetClonedValue(asset, inst);
            wrap.Copy(context, asset, inst);
            return inst;
        }

        #endregion

        #region initialize
        static bool sGotWrappers;
        readonly static HashSet<TypeWrapper> sAllWrappers = new HashSet<TypeWrapper>();
        readonly static Dictionary<string, TypeWrapper> sWrappers = new Dictionary<string, TypeWrapper>();
        readonly static Dictionary<Type, ConstructorInfo> sDefaultFields = new Dictionary<Type, ConstructorInfo>();
        static object SyncLock => sWrappers;

        [InitializeOnLoadMethodParallel(IsOnMainThread = true, ExecutionOrder = InitializeOrder.First - 1)]
        static void ReloadWrappers()
        {
            sGotWrappers = false;
        }

        public static TypeWrapper GetWrapper(Type type)
        {
            var tname = type.FullName;
            if (string.IsNullOrEmpty(tname))
                return null;
            TypeWrapper wrap;
            lock (SyncLock)
            {
                if (sWrappers.TryGetValue(type.FullName, out wrap))
                    return wrap;
            }
            return null;
        }

        public static TypeWrapper GetWrapper(string wrapName)
        {
            TypeWrapper wrap;
            lock (SyncLock)
            {
                if (sWrappers.TryGetValue(wrapName, out wrap))
                    return wrap;
            }
            return null;
        }

        public static void VisitAllWrappers(Action<TypeWrapper> action)
        {
            if (action == null)
                return;
            lock (SyncLock)
            {
                foreach (var wrap in sAllWrappers)
                {
                    action(wrap);
                }
            }
        }

        [InitializeOnLoadMethodParallel]
        public static void InitWrappers()
        {
            lock (SyncLock)
            {
                if (!sGotWrappers)
                {
                    sGotWrappers = true;
                    sWrappers.Clear();
                    sDefaultFields.Clear();
                    sAllWrappers.Clear();
                    var assemblies = AppDomain.CurrentDomain.GetAssemblies();
                    //var defaultFields = new Dictionary<Type, FieldConstructor>();

                    //var cachedWrappers = new Dictionary<Type, TypeWrapper>();
                    var types = new HashSet<Type>();
                    foreach (var assembly in assemblies)
                    {
                        var asmbtypes = assembly.GetTypes();
                        foreach (var type in asmbtypes)
                        {
                            var fieldCons = type.IsAbstract ? null : TypeWrapper.GetFieldConstructor(type);
                            if (fieldCons != null)
                            {
                                var att = type.GetCustomAttributes<DefaultSerializableFieldAttribute>();
                                foreach (var iter in att)
                                {
                                    sDefaultFields[iter.FieldType] = fieldCons;
                                }
                            }
                            types.Add(type);
                        }
                    }

                    foreach (var type in types)
                    {
                        if (type.IsAbstract)
                            continue;
                        var sertype = TypeWrapper.GetSerializableType(type/*, defaultFields*/);
                        if (sertype == ESerializeType.NotSerializable)
                            continue;

                        var constructor = TypeWrapper.GetConstructor(type);
                        if (constructor == null && !type.IsValueType)
                        {
                            continue;
                        }

                        var wrapper = new TypeWrapper(type, constructor);
                        sAllWrappers.Add(wrapper);
                        sWrappers[wrapper.Name] = wrapper;
                        var sers = type.GetCustomAttributes<JsonImporterAttribute>();
                        foreach (var ser in sers)
                        {
                            var formerly = ser.FormerlySerializedAs;
                            if (!string.IsNullOrEmpty(formerly))
                            {
                                TypeWrapper exist;
                                if (sWrappers.TryGetValue(formerly, out exist))
                                {
                                    Debug.LogError($"[JsonObj] The type<{wrapper.Name}> formerly as the same name({formerly}) with {exist.WrapType.FullName}");
                                }
                                else
                                {
                                    wrapper.Alias(formerly);
                                    sWrappers[formerly] = wrapper;
                                }
                            }
                        }
                    }
                }
            }
        }

        //[InitializeOnLoadMethodParallel]
        //static void Test()
        //{
        //    // var cons = TypeWrapper.GetConstructor(typeof(A));
        //    // var const2 = TypeWrapper.GetConstructor(typeof(int));
        //    // Debug.LogFormat("A is seriazable: {0}, int is sersialiable: {1}", cons != null, const2 != null);

        //    //Debug.Log($"Base Type of EBTState is {typeof(EBTState).GetEnumUnderlyingType()}");
        //    InitWrappers();
        //    var set = new HashSet<TypeWrapper>();
        //    foreach (var wrap in sWrappers.Values)
        //    {
        //        if (set.Add(wrap) && wrap.WrapType.GetCustomAttribute<JsonSerializeObjectAttribute>() != null)
        //            Debug.Log(wrap.ToString());
        //    }
        //    Debug.Log("Finish JsonSerializedObject Test");
        //}
        #endregion
    }
#endif
}
